package ext;

import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

//https://blog.csdn.net/m0_60982956/article/details/125939058
public class Crypto {
	public static final byte[] key = "1234567890abcdef".getBytes();

	public static void main(String[] args) throws Exception {

		testJar();

	}

	private static void testText() throws Exception {
		// 原文:

		String message = "Hello, world!";
		System.out.println("Message(原始信息): " + message);

		// 加密:
		byte[] data = message.getBytes();
		byte[] encrypted = encrypt(key, data);
		System.out.println("Encrypted(加密内容): " + Base64.getEncoder().encodeToString(encrypted));

		// 解密:
		byte[] decrypted = decrypt(key, encrypted);
		System.out.println("Decrypted(解密内容): " + new String(decrypted));
	}

	private static void testJar() throws Exception {

		// 128位密钥 = 16 bytes Key:
		byte[] key = "1234567890abcdef".getBytes();
		final String jarPath = "D:/work20220906/java/myopen/springboot2/demo20220728test/target";
		final String jarName = "demo20220728test-1.0.0.jar";
		final String midJar = "demo20220728test-1.0.0.out";
		final String outJarName = "demo20220728test-1.0.0out.jar";
		// 读取原jar文件
		var fileData = Files.readAllBytes(Path.of(jarPath, jarName));
		// 加密
		var encrypted = encrypt(key, fileData);
		// 输出加密后的文件
		Files.write(Path.of(jarPath, midJar), encrypted);
		// 读取加密文件
		fileData = Files.readAllBytes(Path.of(jarPath, midJar));
		// 解密文件
		var decrypted = decrypt(key, fileData);
		// 输出解密后文件
		Files.write(Path.of(jarPath, outJarName), decrypted);

		// https://www.coder.work/article/883865
		// https://stackoverflow.com/questions/28964450/loading-a-jar-dynamically-from-memory
		final Map<String, byte[]> map = new HashMap<>();
		try (JarInputStream is = new JarInputStream(new ByteArrayInputStream(decrypted))) {
			for (;;) {
				JarEntry nextEntry = is.getNextJarEntry();
				if (nextEntry == null)
					break;
				System.out.println(nextEntry);
				final int est = (int) nextEntry.getSize();
				byte[] data = new byte[est > 0 ? est : 1024];
				int real = 0;
				for (int r = is.read(data); r > 0; r = is.read(data, real, data.length - real))
					if (data.length == (real += r))
						data = Arrays.copyOf(data, data.length * 2);
				if (real != data.length)
					data = Arrays.copyOf(data, real);
				map.put("/" + nextEntry.getName(), data);
			}
		}
		final URL u = new URL("buffer", null, -1, "/", new URLStreamHandler() {
			protected URLConnection openConnection(URL u) throws IOException {
				final byte[] data = map.get(u.getFile());
				if (data == null)
					throw new FileNotFoundException(u.getFile());
				return new URLConnection(u) {
					public void connect() throws IOException {
					}

					@Override
					public InputStream getInputStream() throws IOException {
						return new ByteArrayInputStream(data);
					}
				};
			}
		});

		try (URLClassLoader cl = new URLClassLoader("bufferURLClassLoader", new URL[] { u },
				Crypto.class.getClassLoader())) {
			// 加载一个类
			Class<?> clazz = cl.loadClass("com.xue.test.Hello");
			clazz.getMethod("say", String.class).invoke(clazz.getConstructor().newInstance(), "URLClassLoader");
		}
	}

	// 加密:
	public static byte[] encrypt(byte[] key, byte[] input) {
		try {
			// 创建密码对象，需要传入算法/工作模式/填充模式
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

			// 根据key的字节内容，"恢复"秘钥对象
			SecretKey keySpec = new SecretKeySpec(key, "AES");

			// 初始化秘钥:设置加密模式ENCRYPT_MODE
			cipher.init(Cipher.ENCRYPT_MODE, keySpec);

			// 根据原始内容(字节),进行加密
			return cipher.doFinal(input);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	// 解密:
	public static byte[] decrypt(byte[] key, byte[] input) {
		try {
			// 创建密码对象，需要传入算法/工作模式/填充模式
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

			// 根据key的字节内容，"恢复"秘钥对象
			SecretKey keySpec = new SecretKeySpec(key, "AES");

			// 初始化秘钥:设置解密模式DECRYPT_MODE
			cipher.init(Cipher.DECRYPT_MODE, keySpec);

			// 根据原始内容(字节),进行解密
			return cipher.doFinal(input);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}